package cn.simon.free.tools.utils;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Stopwatch;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * http unirest 网络请求(轻便型)
 * <p>
 * 官方文档: {@link http://kong.github.io/unirest-java/#responses}
 * <p>
 * <p>
 * Unirest 默认采用的是gson
 * 1. 自定义序列化（fastJson）方式覆盖原有gson
 * 2. asString()后,手动使用序列化方式(fastJson)转
 * <p>
 * 所有的异常尝试,都只支持正常的返回重试
 *
 * @author simon
 * @date 2020/4/2 16:58
 * @copyright:
 * @modifier
 * @motto a no talent, people who have to study hard
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UnirestUtils {


    private static final Integer MAX_RETRY_COUNT = 3;
    private static final Long SLEEP_TIME = 200L;

    /**
     * @param url     url
     * @param timeOut 超时时间
     * @return 字符串
     */
    public static String get(String url, Long timeOut) {
        return get(url, null, TimeUnit.MILLISECONDS, timeOut, String.class);
    }

    /**
     * @param url    url
     * @param params 请求参数
     * @return 泛型
     */
    public static <T> T get(String url, Object params, Class<T> clazz) {
        return get(url, params, TimeUnit.MILLISECONDS, 200L, clazz);
    }

    /**
     * @param url     url
     * @param params  请求参数
     * @param timeOut 超时时长
     * @param clazz   指定返回的泛型类型
     * @param <T>     泛型类型
     * @return 泛型
     */
    public static <T> T get(String url, Object params, Long timeOut, Class<T> clazz) {
        return get(url, params, TimeUnit.MILLISECONDS, timeOut, clazz);
    }

    /**
     * @param url      url
     * @param params   请求参数
     * @param timeUnit 时间单位
     * @param timeOut  超时时长
     * @param clazz    指定返回的泛型类型
     * @param <T>      泛型类型
     * @return 泛型
     */
    public static <T> T get(String url, Object params, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        return get(url, params, null, timeUnit, timeOut, clazz);
    }

    /**
     * @param url         url
     * @param params      请求参数
     * @param headersMaps header
     * @param timeUnit    超时单位
     * @param timeOut     超时时长
     * @param clazz       指定返回的泛型类型
     * @param <T>         泛型类型
     * @return 泛型
     */
    public static <T> T get(String url, Object params, Map<String, String> headersMaps, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        return get(url, BeanUtils.beanToMap(params), headersMaps, timeUnit, timeOut, clazz);
    }

    /**
     * @param url         url
     * @param paramsMaps  请求参数
     * @param headersMaps header
     * @param timeUnit    超时单位
     * @param timeOut     超时时长
     * @param clazz       指定返回的泛型类型
     * @param <T>         泛型类型
     * @return 泛型
     */
    public static <T> T get(String url, Map<String, Object> paramsMaps, Map<String, String> headersMaps, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        int retryCount = 1;
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            do {
                HttpResponse<String> httpResponse = Unirest.get(url)
                        .headers(headersMaps)
                        .queryString(paramsMaps)
                        .connectTimeout(Math.toIntExact(timeUnit.toMillis(timeOut)))
                        .asString();
                if (httpResponse.isSuccess()) {
                    log.info("[GET]调用:{},成功,返回:{},耗时:{}", url, httpResponse.getBody(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
                    return JSON.parseObject(httpResponse.getBody(), clazz);
                }
                log.info("[GET]调用:{},失败,进行第 {} 次重试,上次失败原因:{}", url, retryCount, httpResponse.getBody());
                Thread.sleep(SLEEP_TIME);
            } while (++retryCount <= MAX_RETRY_COUNT);
        } catch (Exception e) {
            log.error("[GET]调用:{}异常", url, e);
        }
        return null;
    }

    /**
     * @param url     url
     * @param body    body
     * @param timeOut 超时时长
     * @return 字符串
     */
    public static String post(String url, Object body, Long timeOut) {
        return post(url, body, TimeUnit.MILLISECONDS, timeOut, String.class);
    }

    /**
     * @param url     url
     * @param body    body
     * @param timeOut 超时时长
     * @param clazz   指定返回的泛型类型
     * @param <T>     泛型
     * @return 泛型
     */
    public static <T> T post(String url, Object body, Long timeOut, Class<T> clazz) {
        return post(url, body, TimeUnit.MILLISECONDS, timeOut, clazz);
    }

    /**
     * @param url      url
     * @param body     body
     * @param timeUnit 超时单位
     * @param timeOut  超时时长
     * @param clazz    指定返回的泛型类型
     * @param <T>      泛型
     * @return 泛型
     */
    public static <T> T post(String url, Object body, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        return post(url, body, null, timeUnit, timeOut, clazz);
    }

    /**
     * @param url        url
     * @param body       body
     * @param headerMaps header
     * @param timeUnit   超时单位
     * @param timeOut    超时时长
     * @param clazz      指定返回的泛型类型
     * @param <T>        泛型
     * @return 泛型
     */
    public static <T> T post(String url, Object body, Map<String, String> headerMaps, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        return post(url, BeanUtils.beanToMap(body), headerMaps, null, timeUnit, timeOut, clazz);
    }

    /**
     * @param url        url
     * @param bodyMaps   body
     * @param headerMaps header
     * @param paramMaps  ?后面的请求参数
     * @param timeUnit   超时单位
     * @param timeOut    超时时长
     * @param clazz      指定返回的泛型类型
     * @param <T>        泛型
     * @return 泛型
     */
    public static <T> T post(String url, Map<String, Object> bodyMaps, Map<String, String> headerMaps, Map<String, Object> paramMaps, TimeUnit timeUnit, Long timeOut, Class<T> clazz) {
        int retryCount = 1;
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            do {
                HttpResponse<String> httpResponse = Unirest.post(url)
                        .headers(headerMaps)
                        .queryString(paramMaps)
                        .body(bodyMaps)
                        .connectTimeout(Math.toIntExact(timeUnit.toMillis(timeOut)))
                        .asString();
                if (httpResponse.isSuccess()) {
                    log.info("[POST]调用:{},成功,返回:{},耗时:{}", url, httpResponse.getBody(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
                    return JSON.parseObject(httpResponse.getBody(), clazz);
                }
                log.info("[POST]调用:{},失败,进行第 {} 次重试,上次失败原因:{}", url, retryCount, httpResponse.getBody());
                Thread.sleep(SLEEP_TIME);
            } while (++retryCount <= MAX_RETRY_COUNT);
        } catch (Exception e) {
            log.error("[POST]调用:{}异常", url, e);
        }
        return null;
    }

}
